package zauth

import (
	"context"
	"encoding/base64"
	"net/http"
	"strings"
	"sync"
	"time"

	v1 "gitee.com/youkelike/ziam/zapi/apiserver/v1"
	metav1 "gitee.com/youkelike/ziam/zapi/meta/v1"
	"gitee.com/youkelike/ziam/zauth2/middleware"
	"gitee.com/youkelike/ziam/zorm/store"
	"gitee.com/youkelike/zlog"
	jwt "github.com/appleboy/gin-jwt/v2"
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
)

// 这个文件提供获取各种认证策略实例的工厂方法

const (
	APIServerAudience = "api.com"
	APIServerIssuer   = "apiserver"
	AuthzAudience     = "authz.com"
)

type logininfo struct {
	Username string
	Password string
}

var once *sync.Once
var JWTAuthMdw *jwt.GinJWTMiddleware

// 从配置文件中读取 jwt 的 secret，
// 并且用到了插件来完成 jwt 验证过程
func getJWTAuthMdwFunc() *jwt.GinJWTMiddleware {
	once.Do(func() {
		JWTAuthMdw, _ = jwt.New(&jwt.GinJWTMiddleware{
			Realm:            viper.GetString("jwt.realm"),
			SigningAlgorithm: "HS256",
			// 验证 jwt 的 secret key，注意这里是从配置文件中读取的
			Key: []byte(viper.GetString("jwt.key")),
			// Timeout:          viper.GetDuration("jwt.timeout"),
			// MaxRefresh: viper.GetDuration("jwt.max-refresh"),
			MaxRefresh: time.Hour,
			// 进行 basic 登陆认证，在 jwt.LoginHandler 中会调用它来执行 basic 认证，
			// 登录成功之后再用登录信息生成 jwt token
			Authenticator: authenticator(),
			// 登录成功之后的响应
			LoginResponse: loginResponse(),
			LogoutResponse: func(c *gin.Context, code int) {
				c.JSON(http.StatusOK, nil)
			},
			RefreshResponse: refreshResponse(),
			// 用于往 token.claims (也叫 payload) 中添加业务相关字段，
			// 包括 iss、aud、sub、identity 字段，供后面使用。
			PayloadFunc: payloadFunc(),
			// token 验证通过后，这里返回的值会被 c.Set(IdentityKey, returnvalue)
			IdentityHandler: func(c *gin.Context) interface{} {
				claims := jwt.ExtractClaims(c)
				zlog.Infow("=========", zlog.Any("claims", claims))
				return claims[jwt.IdentityKey]
			},
			IdentityKey: middleware.UsernameKey,
			// 认证成功之后的回调
			Authorizator: authorizator(),
			Unauthorized: func(c *gin.Context, code int, message string) {
				c.JSON(code, gin.H{
					"message": message,
				})
			},
			TokenLookup:   "header: Authorization, query: token, cookie: jwt",
			TokenHeadName: "Bearer",
			SendCookie:    true,
			TimeFunc:      time.Now,
		})
	})

	return JWTAuthMdw
}

func LoginHandler() gin.HandlerFunc {
	return getJWTAuthMdwFunc().LoginHandler
}
func LogoutHandler() gin.HandlerFunc {
	return getJWTAuthMdwFunc().LogoutHandler
}
func RefreshHandler() gin.HandlerFunc {
	return getJWTAuthMdwFunc().RefreshHandler
}

// jwt 认证中间件
func NewJWTAuth() gin.HandlerFunc {
	return middleware.NewJWTAuth(getJWTAuthMdwFunc)
}

// basic 认证中间件
func NewBasicAuth() gin.HandlerFunc {
	return middleware.NewBasicAuth(compareFunc)
}

// 自动选择认证策略
func NewAutoAuth() gin.HandlerFunc {
	return middleware.NewAutoAuth(NewBasicAuth(), NewJWTAuth())
}

// 用在 zauth 应用中，
// 从 jwt token 中取出 kid，在从缓存中根据 kid 获取 secret，
// 手动编码验证 jwt 的有效性，
// func NewCacheAuth() gin.HandlerFunc {
// 	return middleware.NewCacheAuth(getSecretFunc)
// }

func compareFunc(username, password string) bool {
	user, err := store.Client().Users().Get(context.Background(), username, metav1.GetOptions{})
	if err != nil {
		return false
	}

	if err := user.Compare(password); err != nil {
		return false
	}

	// 可以更新用户的登陆时间
	user.LoginedAt = time.Now()

	return true
}

// func getSecretFunc(kid string) (middleware.Secret, error) {
// 	// 通过缓存获取数据
// 	cli, err := cache.GetCacheInsOr(nil)
// 	if err != nil {
// 		return middleware.Secret{}, errors.Wrap(err, "get store instance failed")
// 	}

// 	secret, err := cli.GetSecret(kid)
// 	if err != nil {
// 		return middleware.Secret{}, err
// 	}

// 	return middleware.Secret{
// 		Username: secret.Username,
// 		ID:       secret.SecretId,
// 		Key:      secret.SecretKey,
// 		Expires:  secret.Expires,
// 	}, nil

// }

// basic 登陆验证
func authenticator() func(c *gin.Context) (interface{}, error) {
	return func(c *gin.Context) (interface{}, error) {
		var login logininfo
		var err error

		if c.Request.Header.Get("Authorization") != "" {
			login, err = parseWithHeader(c)
		} else {
			login, err = parseWithBody(c)
		}
		if err != nil {
			zlog.Errorw("no Authorization header")
			return "", jwt.ErrFailedAuthentication
		}

		// storeIns := store.Client()
		user, err := store.Client().Users().Get(c, login.Username, metav1.GetOptions{})
		if err != nil {
			zlog.Errorw("%s,%s get user info failed: %s", login, user, err.Error())
			return "", jwt.ErrFailedAuthentication
		}

		if err := user.Compare(login.Password); err != nil {
			zlog.Errorw("password not match: %s,%s,%s", login, user, err.Error())
			return "", jwt.ErrFailedAuthentication
		}

		user.LoginedAt = time.Now()
		return user, nil
	}
}

func parseWithHeader(c *gin.Context) (logininfo, error) {
	auth := strings.SplitN(c.Request.Header.Get("Authorization"), " ", 2)
	if len(auth) != 2 || auth[0] != "Basic" {
		zlog.Errorw("get basic string from Authorization header failed")
		return logininfo{}, jwt.ErrFailedAuthentication
	}

	payload, err := base64.StdEncoding.DecodeString(auth[1])
	if err != nil {
		zlog.Errorw("decode basic string: %s", err.Error())
		return logininfo{}, jwt.ErrFailedAuthentication
	}

	pair := strings.SplitN(string(payload), ":", 2)
	if len(pair) != 2 {
		zlog.Errorw("parse payload failed")
		return logininfo{}, jwt.ErrFailedAuthentication
	}

	return logininfo{Username: pair[0], Password: pair[1]}, nil
}

func parseWithBody(c *gin.Context) (logininfo, error) {
	var login logininfo
	if err := c.ShouldBindJSON(&login); err != nil {
		zlog.Errorw("parse login parameters: %s", err.Error())
		return logininfo{}, jwt.ErrFailedAuthentication
	}

	return login, nil
}

func loginResponse() func(c *gin.Context, code int, token string, expire time.Time) {
	return func(c *gin.Context, code int, token string, expire time.Time) {
		c.JSON(http.StatusOK, gin.H{
			"token":  token,
			"expire": expire.Format(time.RFC3339),
		})
	}
}

func refreshResponse() func(c *gin.Context, code int, token string, expire time.Time) {
	return func(c *gin.Context, code int, token string, expire time.Time) {
		c.JSON(http.StatusOK, gin.H{
			"token":  token,
			"expire": expire.Format(time.RFC3339),
		})
	}
}

func payloadFunc() func(data interface{}) jwt.MapClaims {
	return func(data interface{}) jwt.MapClaims {
		claims := jwt.MapClaims{
			"iss": APIServerIssuer,   // 签发者
			"aud": APIServerAudience, // 接收者，给哪个服务使用
		}
		zlog.Infow("=========", zlog.Any("claims", claims))

		if u, ok := data.(*v1.User); ok {
			claims[jwt.IdentityKey] = u.Name
			claims["sub"] = u.Name // subject 指定用户ID就行
		}

		return claims
	}
}

func authorizator() func(data interface{}, c *gin.Context) bool {
	return func(data interface{}, c *gin.Context) bool {
		if v, ok := data.(string); ok {
			zlog.L(c).Infof("user %s is authenticated.", v)
			return true
		}
		return false
	}
}
